﻿namespace TNet
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Runtime.CompilerServices;

    public class GameClient
    {
        public bool isActive = true;
        private static TNet.Buffer mBuffer;
        private bool mCanPing;
        private int mChannelID;
        private string mData = string.Empty;
        private Dictionary<int, Player> mDictionary = new Dictionary<int, Player>();
        private int mHost;
        private bool mIsInChannel;
        private TNet.List<OnLoadFile> mLoadFiles = new TNet.List<OnLoadFile>();
        private long mMyTime;
        private IPEndPoint mPacketSource;
        private int mPing;
        private long mPingTime;
        private IPEndPoint mServerUdpEndPoint;
        private TcpProtocol mTcp = new TcpProtocol();
        private long mTimeDifference;
        private UdpProtocol mUdp = new UdpProtocol();
        private bool mUdpIsUsable;
        public OnConnect onConnect;
        public OnCreate onCreate;
        public OnDestroy onDestroy;
        public OnDisconnect onDisconnect;
        public OnError onError;
        public OnForwardedPacket onForwardedPacket;
        public OnJoinChannel onJoinChannel;
        public OnLeftChannel onLeftChannel;
        public OnLoadLevel onLoadLevel;
        public OnPing onPing;
        public OnPlayerJoined onPlayerJoined;
        public OnPlayerLeft onPlayerLeft;
        public OnPlayerSync onPlayerSync;
        public OnRenamePlayer onRenamePlayer;
        public OnSetChannelData onSetChannelData;
        public OnSetHost onSetHost;
        public Dictionary<byte, OnPacket> packetHandlers = new Dictionary<byte, OnPacket>();
        public TNet.List<Player> players = new TNet.List<Player>();

        public BinaryWriter BeginSend(byte packetID)
        {
            mBuffer = TNet.Buffer.Create();
            return mBuffer.BeginPacket(packetID);
        }

        public BinaryWriter BeginSend(Packet type)
        {
            mBuffer = TNet.Buffer.Create();
            return mBuffer.BeginPacket(type);
        }

        public void CloseChannel()
        {
            if (this.isConnected && this.isInChannel)
            {
                this.BeginSend(Packet.RequestCloseChannel);
                this.EndSend();
            }
        }

        public void Connect(IPEndPoint externalIP, IPEndPoint internalIP)
        {
            this.Disconnect();
            this.mTcp.Connect(externalIP, internalIP);
        }

        public void Disconnect()
        {
            this.mTcp.Disconnect();
        }

        public void EndSend()
        {
            if (mBuffer != null)
            {
                mBuffer.EndPacket();
                this.mTcp.SendTcpPacket(mBuffer);
                mBuffer.Recycle();
                mBuffer = null;
            }
        }

        public void EndSend(bool reliable)
        {
            mBuffer.EndPacket();
            if ((reliable || !this.mUdpIsUsable) || ((this.mServerUdpEndPoint == null) || !this.mUdp.isActive))
            {
                this.mTcp.SendTcpPacket(mBuffer);
            }
            else
            {
                this.mUdp.Send(mBuffer, this.mServerUdpEndPoint);
            }
            mBuffer.Recycle();
            mBuffer = null;
        }

        public void EndSend(int port)
        {
            mBuffer.EndPacket();
            this.mUdp.Broadcast(mBuffer, port);
            mBuffer.Recycle();
            mBuffer = null;
        }

        public void EndSend(IPEndPoint target)
        {
            mBuffer.EndPacket();
            this.mUdp.Send(mBuffer, target);
            mBuffer.Recycle();
            mBuffer = null;
        }

        public Player GetPlayer(int id)
        {
            if (id == this.mTcp.id)
            {
                return this.mTcp;
            }
            if (this.isConnected)
            {
                Player player = null;
                this.mDictionary.TryGetValue(id, out player);
                return player;
            }
            return null;
        }

        public void JoinChannel(int channelID, string levelName, bool persistent, int playerLimit, string password)
        {
            if (this.isConnected)
            {
                BinaryWriter writer = this.BeginSend(Packet.RequestJoinChannel);
                writer.Write(channelID);
                writer.Write(!string.IsNullOrEmpty(password) ? password : string.Empty);
                writer.Write(!string.IsNullOrEmpty(levelName) ? levelName : string.Empty);
                writer.Write(persistent);
                writer.Write((ushort) playerLimit);
                this.EndSend();
            }
        }

        public void LeaveChannel()
        {
            if (this.isConnected && this.isInChannel)
            {
                this.BeginSend(Packet.RequestLeaveChannel);
                this.EndSend();
            }
        }

        public void LoadFile(string filename, OnLoadFile callback)
        {
            this.mLoadFiles.Add(callback);
            this.BeginSend(Packet.RequestLoadFile).Write(filename);
            this.EndSend();
        }

        public void LoadLevel(string levelName)
        {
            if (this.isConnected && this.isInChannel)
            {
                this.BeginSend(Packet.RequestLoadLevel).Write(levelName);
                this.EndSend();
            }
        }

        public void Ping(IPEndPoint udpEndPoint, OnPing callback)
        {
            this.onPing = callback;
            this.mPingTime = DateTime.UtcNow.Ticks / 0x2710L;
            this.BeginSend(Packet.RequestPing);
            this.EndSend(udpEndPoint);
        }

        private bool ProcessPacket(TNet.Buffer buffer, IPEndPoint ip)
        {
            this.mPacketSource = ip;
            BinaryReader reader = buffer.BeginReading();
            if (buffer.size != 0)
            {
                OnPacket packet2;
                Packet packet = (Packet) reader.ReadByte();
                if ((packet == Packet.ResponseID) || (this.mTcp.stage == TcpProtocol.Stage.Verifying))
                {
                    if (this.mTcp.VerifyResponseID(packet, reader))
                    {
                        this.mTimeDifference = reader.ReadInt64() - (DateTime.UtcNow.Ticks / 0x2710L);
                        if (this.mUdp.isActive)
                        {
                            this.BeginSend(Packet.RequestSetUDP).Write((ushort) this.mUdp.listeningPort);
                            this.EndSend();
                        }
                        this.mCanPing = true;
                        if (this.onConnect != null)
                        {
                            this.onConnect(true, null);
                        }
                    }
                    return true;
                }
                if (this.packetHandlers.TryGetValue((byte) packet, out packet2) && (packet2 != null))
                {
                    packet2(packet, reader, ip);
                    return true;
                }
                switch (packet)
                {
                    case Packet.ResponsePing:
                    {
                        int milliSeconds = (int) (this.mMyTime - this.mPingTime);
                        if (ip == null)
                        {
                            this.mCanPing = true;
                            this.mPing = milliSeconds;
                        }
                        else if ((this.onPing != null) && (ip != null))
                        {
                            this.onPing(ip, milliSeconds);
                        }
                        break;
                    }
                    case Packet.ResponseSetUDP:
                    {
                        ushort port = reader.ReadUInt16();
                        if (port == 0)
                        {
                            this.mServerUdpEndPoint = null;
                        }
                        else
                        {
                            IPAddress address = new IPAddress(this.mTcp.tcpEndPoint.Address.GetAddressBytes());
                            this.mServerUdpEndPoint = new IPEndPoint(address, port);
                            if (this.mUdp.isActive)
                            {
                                mBuffer = TNet.Buffer.Create();
                                mBuffer.BeginPacket(Packet.RequestActivateUDP).Write(this.playerID);
                                mBuffer.EndPacket();
                                this.mUdp.Send(mBuffer, this.mServerUdpEndPoint);
                                mBuffer.Recycle();
                                mBuffer = null;
                            }
                        }
                        break;
                    }
                    case Packet.ResponsePlayerLeft:
                    {
                        Player item = this.GetPlayer(reader.ReadInt32());
                        if (item != null)
                        {
                            this.mDictionary.Remove(item.id);
                        }
                        this.players.Remove(item);
                        if (this.onPlayerLeft != null)
                        {
                            this.onPlayerLeft(item);
                        }
                        break;
                    }
                    case Packet.ResponsePlayerJoined:
                    {
                        Player player4 = new Player {
                            id = reader.ReadInt32(),
                            name = reader.ReadString(),
                            data = reader.ReadObject()
                        };
                        this.mDictionary.Add(player4.id, player4);
                        this.players.Add(player4);
                        if (this.onPlayerJoined != null)
                        {
                            this.onPlayerJoined(player4);
                        }
                        break;
                    }
                    case Packet.ResponseJoiningChannel:
                    {
                        this.mIsInChannel = true;
                        this.mDictionary.Clear();
                        this.players.Clear();
                        this.mChannelID = reader.ReadInt32();
                        int num4 = reader.ReadInt16();
                        for (int i = 0; i < num4; i++)
                        {
                            Player player2 = new Player {
                                id = reader.ReadInt32(),
                                name = reader.ReadString(),
                                data = reader.ReadObject()
                            };
                            this.mDictionary.Add(player2.id, player2);
                            this.players.Add(player2);
                        }
                        break;
                    }
                    case Packet.ResponseJoinChannel:
                        this.mIsInChannel = reader.ReadBoolean();
                        if (this.onJoinChannel != null)
                        {
                            this.onJoinChannel(this.mIsInChannel, !this.mIsInChannel ? reader.ReadString() : null);
                        }
                        break;

                    case Packet.ResponseLeaveChannel:
                        this.mData = string.Empty;
                        this.mChannelID = 0;
                        this.mIsInChannel = false;
                        this.mDictionary.Clear();
                        this.players.Clear();
                        if (this.onLeftChannel != null)
                        {
                            this.onLeftChannel();
                        }
                        break;

                    case Packet.ResponseRenamePlayer:
                    {
                        Player p = this.GetPlayer(reader.ReadInt32());
                        string name = p.name;
                        if (p != null)
                        {
                            p.name = reader.ReadString();
                        }
                        if (this.onRenamePlayer != null)
                        {
                            this.onRenamePlayer(p, name);
                        }
                        break;
                    }
                    case Packet.ResponseSetHost:
                        this.mHost = reader.ReadInt32();
                        if (this.onSetHost != null)
                        {
                            this.onSetHost(this.isHosting);
                        }
                        break;

                    case Packet.ResponseLoadLevel:
                        if (this.onLoadLevel != null)
                        {
                            this.onLoadLevel(reader.ReadString());
                        }
                        return false;

                    case Packet.ResponseCreate:
                        if (this.onCreate != null)
                        {
                            int creator = reader.ReadInt32();
                            ushort index = reader.ReadUInt16();
                            uint objID = reader.ReadUInt32();
                            this.onCreate(creator, index, objID, reader);
                        }
                        break;

                    case Packet.ResponseDestroy:
                        if (this.onDestroy != null)
                        {
                            int num9 = reader.ReadUInt16();
                            for (int j = 0; j < num9; j++)
                            {
                                this.onDestroy(reader.ReadUInt32());
                            }
                        }
                        break;

                    case Packet.ResponseLoadFile:
                    {
                        string filename = reader.ReadString();
                        int count = reader.ReadInt32();
                        byte[] data = reader.ReadBytes(count);
                        OnLoadFile file = this.mLoadFiles.Pop();
                        if (file != null)
                        {
                            file(filename, data);
                        }
                        break;
                    }
                    case Packet.ResponseSetChannelData:
                        this.mData = reader.ReadString();
                        if (this.onSetChannelData != null)
                        {
                            this.onSetChannelData(this.mData);
                        }
                        break;

                    case Packet.ForwardToAll:
                    case Packet.ForwardToAllSaved:
                    case Packet.ForwardToOthers:
                    case Packet.ForwardToOthersSaved:
                    case Packet.ForwardToHost:
                    case Packet.Broadcast:
                        if (this.onForwardedPacket != null)
                        {
                            this.onForwardedPacket(reader);
                        }
                        break;

                    case Packet.ForwardToPlayer:
                        reader.ReadInt32();
                        if (this.onForwardedPacket != null)
                        {
                            this.onForwardedPacket(reader);
                        }
                        break;

                    case Packet.SyncPlayerData:
                    {
                        Player player = this.GetPlayer(reader.ReadInt32());
                        if (player != null)
                        {
                            player.data = reader.ReadObject();
                            if (this.onPlayerSync != null)
                            {
                                this.onPlayerSync(player);
                            }
                        }
                        break;
                    }
                    case Packet.ForwardByName:
                        reader.ReadString();
                        if (this.onForwardedPacket != null)
                        {
                            this.onForwardedPacket(reader);
                        }
                        break;

                    case Packet.Error:
                        if ((this.mTcp.stage == TcpProtocol.Stage.Connected) || (this.onConnect == null))
                        {
                            if (this.onError != null)
                            {
                                this.onError(reader.ReadString());
                            }
                            break;
                        }
                        this.onConnect(false, reader.ReadString());
                        break;

                    case Packet.Disconnect:
                        this.mData = string.Empty;
                        if (this.isInChannel && (this.onLeftChannel != null))
                        {
                            this.onLeftChannel();
                        }
                        this.players.Clear();
                        this.mDictionary.Clear();
                        this.mTcp.Close(false);
                        this.mLoadFiles.Clear();
                        if (this.onDisconnect != null)
                        {
                            this.onDisconnect();
                        }
                        break;
                }
            }
            return true;
        }

        public void ProcessPackets()
        {
            this.mMyTime = DateTime.UtcNow.Ticks / 0x2710L;
            if ((this.mTcp.isConnected && this.mCanPing) && ((this.mPingTime + 0xfa0L) < this.mMyTime))
            {
                this.mCanPing = false;
                this.mPingTime = this.mMyTime;
                this.BeginSend(Packet.RequestPing);
                this.EndSend();
            }
            TNet.Buffer buffer = null;
            bool flag = true;
            IPEndPoint source = null;
            while ((flag && this.isActive) && this.mUdp.ReceivePacket(out buffer, out source))
            {
                this.mUdpIsUsable = true;
                flag = this.ProcessPacket(buffer, source);
                buffer.Recycle();
            }
            while ((flag && this.isActive) && this.mTcp.ReceivePacket(out buffer))
            {
                flag = this.ProcessPacket(buffer, null);
                buffer.Recycle();
            }
        }

        public void SaveFile(string filename, byte[] data)
        {
            BinaryWriter writer = this.BeginSend(Packet.RequestSaveFile);
            writer.Write(filename);
            writer.Write(data.Length);
            writer.Write(data);
            this.EndSend();
        }

        public void SetHost(Player player)
        {
            if ((this.isConnected && this.isInChannel) && this.isHosting)
            {
                this.BeginSend(Packet.RequestSetHost).Write(player.id);
                this.EndSend();
            }
        }

        public void SetPlayerLimit(int max)
        {
            if (this.isConnected && this.isInChannel)
            {
                this.BeginSend(Packet.RequestSetPlayerLimit).Write((ushort) max);
                this.EndSend();
            }
        }

        public void SetTimeout(int seconds)
        {
            if (this.isConnected)
            {
                this.BeginSend(Packet.RequestSetTimeout).Write(seconds);
                this.EndSend();
            }
        }

        public bool StartUDP(int udpPort)
        {
            if (!this.mUdp.Start(udpPort))
            {
                return false;
            }
            if (this.isConnected)
            {
                this.BeginSend(Packet.RequestSetUDP).Write((ushort) udpPort);
                this.EndSend();
            }
            return true;
        }

        public void StopUDP()
        {
            if (this.mUdp.isActive)
            {
                if (this.isConnected)
                {
                    this.BeginSend(Packet.RequestSetUDP).Write((ushort) 0);
                    this.EndSend();
                }
                this.mUdp.Stop();
                this.mUdpIsUsable = false;
            }
        }

        public void SyncPlayerData()
        {
            if (this.isConnected)
            {
                BinaryWriter bw = this.BeginSend(Packet.SyncPlayerData);
                bw.Write(this.mTcp.id);
                bw.WriteObject(this.mTcp.data);
                this.EndSend();
            }
        }

        public bool canUseUDP
        {
            get
            {
                return (this.mUdp.isActive && (this.mServerUdpEndPoint != null));
            }
        }

        public string channelData
        {
            get
            {
                return (!this.isInChannel ? string.Empty : this.mData);
            }
            set
            {
                if ((this.isHosting && this.isInChannel) && !this.mData.Equals(value))
                {
                    this.mData = value;
                    this.BeginSend(Packet.RequestSetChannelData).Write(value);
                    this.EndSend();
                }
            }
        }

        public int channelID
        {
            get
            {
                return this.mChannelID;
            }
        }

        public int hostID
        {
            get
            {
                return (!this.mTcp.isConnected ? this.mTcp.id : this.mHost);
            }
        }

        public bool isConnected
        {
            get
            {
                return this.mTcp.isConnected;
            }
        }

        public bool isHosting
        {
            get
            {
                return (!this.mIsInChannel || (this.mHost == this.mTcp.id));
            }
        }

        public bool isInChannel
        {
            get
            {
                return this.mIsInChannel;
            }
        }

        public bool isTryingToConnect
        {
            get
            {
                return this.mTcp.isTryingToConnect;
            }
        }

        public int listeningPort
        {
            get
            {
                return this.mUdp.listeningPort;
            }
        }

        public bool noDelay
        {
            get
            {
                return this.mTcp.noDelay;
            }
            set
            {
                if (this.mTcp.noDelay != value)
                {
                    this.mTcp.noDelay = value;
                    this.BeginSend(Packet.RequestNoDelay).Write(value);
                    this.EndSend();
                }
            }
        }

        public IPEndPoint packetSource
        {
            get
            {
                return ((this.mPacketSource == null) ? this.mTcp.tcpEndPoint : this.mPacketSource);
            }
        }

        public int ping
        {
            get
            {
                return (!this.isConnected ? 0 : this.mPing);
            }
        }

        public Player player
        {
            get
            {
                return this.mTcp;
            }
        }

        public object playerData
        {
            get
            {
                return this.mTcp.data;
            }
            set
            {
                this.mTcp.data = value;
                this.SyncPlayerData();
            }
        }

        public int playerID
        {
            get
            {
                return this.mTcp.id;
            }
        }

        public string playerName
        {
            get
            {
                return this.mTcp.name;
            }
            set
            {
                if (this.mTcp.name != value)
                {
                    if (this.isConnected)
                    {
                        this.BeginSend(Packet.RequestSetName).Write(value);
                        this.EndSend();
                    }
                    else
                    {
                        this.mTcp.name = value;
                    }
                }
            }
        }

        public long serverTime
        {
            get
            {
                return (this.mTimeDifference + (DateTime.UtcNow.Ticks / 0x2710L));
            }
        }

        public delegate void OnConnect(bool success, string message);

        public delegate void OnCreate(int creator, int index, uint objID, BinaryReader reader);

        public delegate void OnDestroy(uint objID);

        public delegate void OnDisconnect();

        public delegate void OnError(string message);

        public delegate void OnForwardedPacket(BinaryReader reader);

        public delegate void OnJoinChannel(bool success, string message);

        public delegate void OnLeftChannel();

        public delegate void OnLoadFile(string filename, byte[] data);

        public delegate void OnLoadLevel(string levelName);

        public delegate void OnPacket(Packet response, BinaryReader reader, IPEndPoint source);

        public delegate void OnPing(IPEndPoint ip, int milliSeconds);

        public delegate void OnPlayerJoined(Player p);

        public delegate void OnPlayerLeft(Player p);

        public delegate void OnPlayerSync(Player p);

        public delegate void OnRenamePlayer(Player p, string previous);

        public delegate void OnSetChannelData(string data);

        public delegate void OnSetHost(bool hosting);
    }
}

